import java.util.*;

public class Example {
    public static void main(String args[]) {
        System.out.println("这是一个24点游戏。");
        System.out.println("程序将随机给出四张牌。其中，A代表1，J代表11，Q代表12，K代表13。");
        System.out.println("请尝试计算24点，并将算式写出。");
        System.out.println("注意：\n1.请不要输入空格\n2.程序不保证给出的四张牌一定可以算出24点\n" +
                "3.允许使用+,-,*,/,%,^,(,)\n可以输入\"#\"退出，输入\"*\"产生新的组合。");
        Scanner in = new Scanner(System.in);
        String infix = null;
        String card;    //花色与数字的组合
        String[] suit = {"红桃", "黑桃", "方块", "梅花"};
        String[] number = {"A", "2", "3", "4", "5", "6", "7", "8", "9", "10", "J", "Q", "K"};
        boolean newCombination = true;
        ArrayList<String> selectedNums = new ArrayList<>();
        ArrayList<String> selectedCards = new ArrayList<>();
        Random r = new Random();
        int suitIndex;  //suit n.花色
        int numberIndex;
        while (true) {
            //生成四张牌
            if (newCombination == true) {
                newCombination = false;
                selectedCards.clear();
                selectedNums.clear();
                suitIndex = r.nextInt(4);   //suit n.花色
                numberIndex = r.nextInt(13);
                card = suit[suitIndex] + number[numberIndex];
                while (selectedCards.size() < 4) {
                    //未产生过的组合
                    if (!selectedCards.contains(card)) {
                        selectedCards.add(card);
                        selectedNums.add(String.valueOf(getCardNum(number[numberIndex])));
                    }
                    suitIndex = r.nextInt(4);
                    numberIndex = r.nextInt(13);
                    card = suit[suitIndex] + number[numberIndex];
                }
                System.out.println(selectedCards);
                System.out.print(" >");
                infix = in.nextLine();
            }

            if (infix.charAt(0) == '#') break;
            else if (infix.charAt(0) == '*') {
                newCombination = true;
                continue;
            }

            //处理用户输入
            ArrayList<String> postfix = returnPostfixAdvanced(infix);
            //检查用户输入的数字是否与程序生成的相同
            if (!checkUserNums(postfix, selectedNums)) {
                System.out.println("您似乎未按照给出的纸牌输入，请重试。");
                System.out.print(" >");
                infix = in.nextLine();
                continue;
            }
            //计算表达式
            double result = calcPostfixAdvanced(postfix);
            System.out.println("您输入的算式返回" + result);
            //未能返回正确结果
            if (result != 24) {
                System.out.println("很遗憾，您的输入未返回正确的结果。您可以尝试再次输入，重新生成组合，或者退出。");
                System.out.print(" >");
                infix = in.nextLine();
                continue;
            }
            //返回正确结果
            else {
                System.out.println("真棒！您的输入返回了正确的结果。还要再来一轮吗？(请输入Y或N)");
                System.out.print(" >");
                infix = in.nextLine();
                if (infix.charAt(0) == 'Y' || infix.charAt(0) == 'y') {
                    newCombination = true;
                    continue;
                }
                else if (infix.charAt(0) == 'N' || infix.charAt(0) == 'n') break;
            }
        }
        System.out.println("感谢体验此游戏！");
    }

    //高级求后缀表达式函数，操作符可以为+,-,*,/,%,^,(,)
    //仅用于计算24点：数字只支持最多两位数
    //可以较为方便地扩展为一般的求后缀表达式函数
    private static ArrayList<String> returnPostfixAdvanced(String infix) {
        //遇到数字直接输出，遇到符号则弹出所有优先级更高的符号，并将此符号入栈。
        //特别的，左括号(压入栈前具有最低优先级，压入栈后优先级将为最低；到右括号时，弹出直到对应的左括号为止的所有符号。
        //扫描到前缀表达式的末尾，弹出所有仍在栈中的符号。
        //取余(%)的优先级与乘除相同，乘方(^)优先级最高
        Stack<Character> stack = new Stack<>();
        ArrayList<String> postfix = new ArrayList<>();
        for (int i = 0; i <= infix.length() - 1; i++) {
            char op = infix.charAt(i);
            //数字
            if (isNum(op)) {
                String num = String.valueOf(op);
                //特殊情况：10,11,12,13
                if (i < (infix.length() - 1) && isNum(infix.charAt(i + 1))) {
                    i++;
                    num += infix.charAt(i);
                }
                postfix.add(num);
                continue;
            }
            //操作符
            // +,-
            if (op == '+' || op == '-') {
                while (!stack.empty()) {
                    char top = stack.peek();
                    if (top != '(') postfix.add(String.valueOf(stack.pop()));
                    else break;
                }
                stack.push(op);
            }
            // *,/,%
            else if (op == '*' || op == '/' || op == '%') {
                while (!stack.empty()) {
                    char top = stack.peek();
                    if (top != '+' && top != '-' && top != '(') postfix.add(String.valueOf(stack.pop()));
                    else break;
                }
                stack.push(op);
            }
            // ^
            else if (op == '^') {
                while (!stack.empty()) {
                    char top = stack.peek();
                    if (top == '^') postfix.add(String.valueOf(stack.pop()));
                    else break;
                }
                stack.push(op);
            }
            // (
            else if (op == '(') {
                stack.push(op);
            }
            // )
            else {
                char top = stack.peek();
                while (top != '(') {
                    postfix.add(String.valueOf(stack.pop()));
                    top = stack.peek();
                }
                //top=='('
                stack.pop();
            }
        }
        //弹出仍在栈中的操作符
        while (!stack.empty()) {
            postfix.add(String.valueOf(stack.pop()));
        }
        return postfix;
    }

    //高级后缀表达式求值，后缀表达式中的操作符可以为+,-,*,/,%,^,(,)
    private static double calcPostfixAdvanced(ArrayList<String> postfix) {
        Stack<Double> stack = new Stack<>();
        for (String str : postfix) {
            //数字
            if (isNum(str)) {
                stack.push(Double.valueOf(str));
                continue;
            }
            //操作符
            double op2 = Double.valueOf(stack.pop());
            double op1 = Double.valueOf(stack.pop());
            if (str.equals("+")) stack.push(op1 + op2);
            else if (str.equals("-")) stack.push(op1 - op2);
            else if (str.equals("*")) stack.push(op1 * op2);
            else if (str.equals("/")) stack.push(op1 / op2);
            else if (str.equals("%")) stack.push(Double.valueOf((int) op1 % (int) op2));
            else stack.push(Math.pow(op1, op2));
        }
        double ret = stack.pop();
        //最多保留四位小数
        ret = (int) (ret * 10000) / 10000.0;
        return ret;
    }

    private static boolean isNum(char ch) {
        if (ch >= '0' && ch <= '9') return true;
        else return false;
    }

    private static boolean isNum(String str) {
        if (str.equals("1") || str.equals("2") || str.equals("3") || str.equals("4") ||
                str.equals("5") || str.equals("6") || str.equals("7") || str.equals("8") ||
                str.equals("9") || str.equals("10") || str.equals("11") || str.equals("12") ||
                str.equals("13")) return true;
        else return false;
    }

    private static boolean checkUserNums(ArrayList<String> postfix, ArrayList<String> selectedNums) {
        ArrayList<String> userNums = new ArrayList<>();
        for (String str : postfix) {
            if (isNum(str)) userNums.add(str);
        }
        if (userNums.size() != 4) return false;
        Collections.sort(selectedNums);
        Collections.sort(userNums);
        return selectedNums.equals(userNums);
    }

    private static int getCardNum(String str) {
        if (str.equals("A")) return 1;
        else if (str.equals("2")) return 2;
        else if (str.equals("3")) return 3;
        else if (str.equals("4")) return 4;
        else if (str.equals("5")) return 5;
        else if (str.equals("6")) return 6;
        else if (str.equals("7")) return 7;
        else if (str.equals("8")) return 8;
        else if (str.equals("9")) return 9;
        else if (str.equals("10")) return 10;
        else if (str.equals("J")) return 11;
        else if (str.equals("Q")) return 12;
        else return 13;
    }

    //基础的求后缀表达式函数，操作符仅限+,-,*,/,(,)
    //存在bug
//    private static ArrayList<Character> returnPostfix(String infix) {
//        //遇到数字直接输出，遇到符号则弹出所有优先级更高的符号，并将此符号入栈。
//        //特别的，左括号(压入栈前具有最低优先级，压入栈后优先级将为最低；到右括号时，弹出直到对应的左括号为止的所有符号。
//        //扫描到前缀表达式的末尾，弹出所有仍在栈中的符号。
//        Stack<Character> stack = new Stack<>();
//        ArrayList<Character> postfix = new ArrayList<>();
//        for (int i = 0; i <= infix.length() - 1; i++) {
//            char op = infix.charAt(i);
//            //数字
//            if (isNum(op)) {
//                if(infix.charAt(i+1)=='0')
//                postfix.add(op);
//                continue;
//            }
//            //操作符
//            // +,-
//            if (op == '+' || op == '-') {
//                while (!stack.empty()) {
//                    char top = stack.peek();
//                    if (top != '(') postfix.add(stack.pop());
//                    else break;
//                }
//                stack.push(op);
//            }
//            // *,/
//            else if (op == '*' || op == '/') {
//                while (!stack.empty()) {
//                    char top = stack.peek();
//                    if (top == '*' || top == '/') postfix.add(stack.pop());
//                    else break;
//                }
//                stack.push(op);
//            }
//            // (
//            else if (op == '(') {
//                stack.push(op);
//            }
//            // )
//            else {
//                char top = stack.peek();
//                while (top != '(') {
//                    postfix.add(stack.pop());
//                    top = stack.peek();
//                }
//                //top=='('
//                stack.pop();
//            }
//        }
//        //弹出仍在栈中的操作符
//        while (!stack.empty()) {
//            postfix.add(stack.pop());
//        }
//        return postfix;
//    }


    //基础的后缀表达式求值，后缀表达式中的操作符仅限+,-,*,/,(,)
    //不完善的函数
//    private static double calcPostfix(ArrayList<Character> postfix) {
//        //将数字逐个推入栈，当扫描到一个运算符，则弹出栈顶的两个数字进行计算，然后将运算结果入栈
//        //后缀表达式扫描完成后，栈中剩下的一个数字即为运算结果
//        //注意：先入栈的数字作为左操作数
//        Stack<Double> stack = new Stack<>();
//        for (char ch : postfix) {
//            //数字
//            if (isNum(ch)) {
//                stack.push(Double.valueOf(ch - '0'));
//                continue;
//            }
//            //操作符
//            double op2 = Double.valueOf(stack.pop());
//            double op1 = Double.valueOf(stack.pop());
//            if (ch == '+') stack.push(op1 + op2);
//            else if (ch == '-') stack.push(op1 - op2);
//            else if (ch == '*') stack.push(op1 * op2);
//            else stack.push(op1 / op2);
//        }
//        double ret = stack.pop();
//        //最多保留四位小数
//        ret = (int) (ret * 10000) / 10000.0;
//        return ret;
//    }
}